// Copyright (c) 2011 SameGoal LLC.
// All Rights Reserved.
// Author: Andy Hochhaus <ahochhaus@samegoal.com>

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "net/http/server/comet/json.h"

#include <gflags/gflags.h>
#include <glog/logging.h>

#include <string>

#include "base/basictypes.h"

namespace sg {
namespace net {
namespace http {
namespace server {
namespace comet {

ItemRecord::ItemRecord() : next(NULL), type_(UNKNOWN) {}

JsonArray::JsonArray() : first_(NULL), last_(NULL) {
  type_ = ARRAY;
}

void JsonArray::AppendString(const std::string &s) {
  Append_(new JsonString(s, true));
}

void JsonArray::AppendNumber(const double d) {
  char val[32];
  // TODO(ahochhaus): encode fractional numbers too
  snprintf(val, sizeof(val), "%d", static_cast<int>(d));
  Append_(new JsonString(std::string(val)));
}

void JsonArray::AppendBoolean(const bool b) {
  if (b) {
    Append_(new JsonString("true"));
  } else {
    Append_(new JsonString("false"));
  }
}

void JsonArray::AppendBytes(const std::string &s) {
  Append_(new JsonString(s));
}

JsonArray *JsonArray::AppendArray() {
  JsonArray *temp = new JsonArray();
  Append_(temp);
  return temp;
}

// TODO(ahochhaus): Make Serialize() performance reasonable by doing a single
// copy.
std::string JsonArray::Serialize() {
  ItemRecord *cur = first_;
  std::string rtn = "[";
  while (cur != NULL) {
    if (cur != first_) {
      rtn += ",";
    }
    rtn += cur->Serialize();
    cur = cur->next;
  }
  rtn += "]\n";
  return rtn;
}

void JsonArray::Append_(ItemRecord *item) {
  if (last_ != NULL) {
    last_->next = item;
  }
  last_ = item;
  if (first_ == NULL) {
    first_ = last_;
  }
}

JsonString::JsonString(const std::string &s, bool quote) : s_() {
  if (quote) {
    const uint64 str_len = s.length();
    s_.reserve(str_len * 4 + 2);
    s_.push_back('\"');

    for (uint64_t i = 0; i < str_len; ++i) {
      const char c = s[i];
      uint64 bytes_to_copy = 0;
      if ((c & 0x80) /* 0b10000000 */ == 0x00 /* 0b00000000 */ &&
          i < str_len) {
        switch (c) {
          case '\"':
            s_.push_back('\\');
            s_.push_back('\"');
            break;
          case '\\':
            s_.push_back('\\');
            s_.push_back('\\');
            break;
          case '\b':
            s_.push_back('\\');
            s_.push_back('b');
            break;
          case '\f':
            s_.push_back('\\');
            s_.push_back('f');
            break;
          case '\n':
            s_.push_back('\\');
            s_.push_back('n');
            break;
          case '\r':
            s_.push_back('\\');
            s_.push_back('r');
            break;
          case '\t':
            s_.push_back('\\');
            s_.push_back('t');
            break;
          case 0x00:
          case 0x01:
          case 0x02:
          case 0x03:
          case 0x04:
          case 0x05:
          case 0x06:
          case 0x07:
          case 0x0b:
          case 0x0e:
          case 0x0f:
          case 0x10:
          case 0x11:
          case 0x12:
          case 0x13:
          case 0x14:
          case 0x15:
          case 0x16:
          case 0x17:
          case 0x18:
          case 0x19:
          case 0x1a:
          case 0x1b:
          case 0x1c:
          case 0x1d:
          case 0x1e:
          case 0x1f:
            {
              char json_escaped_buf[7];
              CHECK_EQ(6, snprintf(json_escaped_buf,
                                   sizeof(json_escaped_buf),
                                   "\\u%04x",
                                   c));
              s_.append(json_escaped_buf);
            }
            break;
          default:
            s_.push_back(c);
            break;
        }
        continue;
      } else if ((c & 0xe0) /* 0b11100000 */ == 0xc0 /* 0b11000000 */ &&
                 i + 1 < str_len) {
        bytes_to_copy = 2;
      } else if ((c & 0xf0) /* 0b11110000 */ == 0xe0 /* 0b11100000 */ &&
                 i + 2 < str_len) {
        bytes_to_copy = 3;
      } else if ((c & 0xf8) /* 0b11111000 */ == 0xf0 /* 0b11110000 */ &&
                 i + 3 < str_len) {
        bytes_to_copy = 4;
      } else {
        LOG(ERROR) << "illegal unicode character: " << static_cast<uint32> (c);
        bytes_to_copy = 1;
      }
      for (uint64 j = 0; j < bytes_to_copy; ++j) {
        s_.push_back(s[i + j]);
      }
      i += (bytes_to_copy - 1);
    }

    s_.push_back('\"');
  } else {
    s_ = s;
  }
}

std::string JsonString::Serialize() {
  return s_;
}

}  // namespace comet
}  // namespace server
}  // namespace http
}  // namespace net
}  // namespace sg
